package tcln.sors.client.core.mvc.modelmapping;

import tcln.commons.annotations.SystemProperty;
import tcln.commons.assertion.Assert;
import tcln.commons.lang.BooleanUtils;
import tcln.commons.systemconfig.SystemPropertiesLoader;

/**
 * Factory class which provides environment-specific {@link ModelDataMapper}s and {@link ModelDataPopulator}s.
 * 
 * @author Truong Chau Lien Ngoc (lienngoc@gmail.com)
 */
public class ModelDataMappingFactory {

    /**
     * Mapping strategies.
     * 
     * @author Truong Chau Lien Ngoc (lienngoc@gmail.com)
     */
    public enum Strategy {

        /**
         * Default strategy: use {@link DefaultModelDataMapper} and ???.
         */
        DEFAULT
    }

    private static volatile ModelDataMappingFactory instance;

    @SystemProperty(name = "sors.system.modelmapping.strategy", custom = true)
    private Strategy strategy;

    @SystemProperty(name = "sors.system.modelmapping.singleton", custom = true)
    private Boolean singletonMapper;

    @SystemProperty(name = "sors.system.modelmapping.singleton", custom = true)
    private Boolean singletonPopulator;

    private static final ThreadLocal<ModelDataMapper> modelDataMapper = new ThreadLocal<ModelDataMapper>();
    private static final ThreadLocal<ModelDataPopulator> modelDataPopulator = new ThreadLocal<ModelDataPopulator>();

    private ModelDataMappingFactory() {
        SystemPropertiesLoader<ModelDataMappingFactory> loader = new SystemPropertiesLoader<ModelDataMappingFactory>(
                ModelDataMappingFactory.class);
        loader.load(this);

        if (strategy == null) {
            strategy = Strategy.DEFAULT;
        }
    }

    /**
     * @return
     */
    public static ModelDataMappingFactory getInstance() {
        if (instance == null) {
            synchronized (ModelDataMappingFactory.class) {
                instance = new ModelDataMappingFactory();
            }
        }
        return instance;
    }

    /**
     * Returns a {@link ModelDataMapper} according to system-configured strategy and attributes.
     * <p>
     * If flag {@link #singletonMapper} is set, a thread-safe mapper implementation instance will be returned or
     * created. Otherwise, a new instance is created everytime this method is invoked.
     * 
     * @return
     */
    public ModelDataMapper getMapper() {
        ModelDataMapper mapper = null;
        if (BooleanUtils.isTrue(singletonMapper)) {
            mapper = modelDataMapper.get();
            if (mapper == null) {
                mapper = createMapperInternal();
                modelDataMapper.set(mapper);
            }
        } else {
            mapper = createMapperInternal();
        }
        return mapper;
    }

    /**
     * Returns a {@link ModelDataPopulator} according to system-configured strategy and attributes.
     * <p>
     * If flag {@link #singletonPopulator} is set, a thread-safe mapper implementation instance will be returned or
     * created. Otherwise, a new instance is created everytime this method is invoked.
     * 
     * @return
     */
    public ModelDataPopulator getPopulator() {
        ModelDataPopulator populator = null;
        if (BooleanUtils.isTrue(singletonPopulator)) {
            populator = modelDataPopulator.get();
            if (populator == null) {
                populator = createPopulatorInternal();
                modelDataPopulator.set(populator);
            }
        } else {
            populator = createPopulatorInternal();
        }
        return populator;
    }

    private ModelDataMapper createMapperInternal() {
        switch (strategy) {
            case DEFAULT:
                return new DefaultModelDataMapper();
            default:
                Assert.STATE.fail("Invalid strategy '{0}'", strategy);
                break;
        }
        return null;
    }

    private ModelDataPopulator createPopulatorInternal() {
        switch (strategy) {
            case DEFAULT:
                return new DefaultModelDataPopulator();
            default:
                Assert.STATE.fail("Invalid strategy '{0}'", strategy);
                break;
        }
        return null;
    }
}
